﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using Noris.Tools.TraceVisualiser.BlockingVisualiser.Data;
using Noris.Tools.TraceVisualiser.Components;

namespace Noris.Tools.TraceVisualiser.BlockingVisualiser.Components
{
    /// <summary>
    /// Panel pro zobrazení dat o deadlocku
    /// </summary>
    internal class LockInfoPanel : Noris.Tools.TraceVisualiser.Components.DblGraphPanel
	{
		#region Konstrukce
		public LockInfoPanel()
		{
			Init();
		}
		protected void Init()
		{
			this._Sheet = new DblGraphPanel() { Bounds = new System.Drawing.Rectangle(0, 0, 750, 450), BackColor = this.BackColor, TabStop = true };
            this.Padding = new Padding(12);

			this.Controls.Add(this._Sheet);

			this.AutoScroll = true;
            this.AutoScrollMargin = new Size(20, 26);
			this.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;

			this._Sheet.PaintToBuffer += new PaintEventHandler(_PaintSheet);
		}

        void _PaintSheet(object sender, PaintEventArgs e)
        {
            this._PaintRelations(e.Graphics);
        }
		private Noris.Tools.TraceVisualiser.Components.DblGraphPanel _Sheet;
		#endregion
		#region Public property a eventy
		/// <summary>
		/// Data o zámku. Vložení data vyvolá jejich zobrazení podle aktuálního configu.
		/// </summary>
        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public SqlLockData LockData
        {
            get { return this._LockData; }
            set
            {
                this._LockData = value;
				this._ShowData();
            }
        }
		/// <summary>
		/// Data s konfigurací. Vložení hodnoty vyvolá zobrazení aktuálních dat podle zadaného configu.
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public SqlLockConfig Config
		{
			get { return this._Config; }
			set
			{
				this._Config = value;
				this._ShowData();
			}
		}
        /// <summary>
        /// Velikost obsahu. Pokud bude this.Size = this.ContentSize, pak se nebudou zobrazovat ScrollBary.
        /// </summary>
        public Size ContentSize
        {
            get { return this._ContentSize; }
            set
            {
                if (value.Width != this._ContentSize.Width || value.Height != this._ContentSize.Height)
                {
                    this._ContentSize = value;
                    if (this.ContentSizeChanged != null)
                        this.ContentSizeChanged(this, EventArgs.Empty);
                }
            }
        }
        private Size _ContentSize;
        /// <summary>
        /// Handler který je volán po změně hodnoty v ContentSize
        /// </summary>
        public event EventHandler ContentSizeChanged;
		/// <summary>
		/// Sumární informace o procesech (do StatusBaru) 
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string SummaryProcess { get; protected set; }
		/// <summary>
		/// Sumární informace o SPIDech (do StatusBaru) 
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string SummarySpid { get; protected set; }
		/// <summary>
		/// Sumární informace o resourcech (do StatusBaru) 
		/// </summary>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string SummaryResource { get; protected set; }
		private SqlLockData _LockData;
		private SqlLockConfig _Config;
		/// <summary>
		/// Objekt, který představuje oběť deadlocku
		/// </summary>
		private LockInfoList _VictimList;
        /// <summary>
        /// Soupis typových odkazů na listy procesů
        /// </summary>
        private List<LockInfoList> _ProcessListList;
        /// <summary>
        /// Soupis typových odkazů na listy SPID
        /// </summary>
        private List<LockInfoList> _SpidListList;
        /// <summary>
        /// Soupis typových odkazů na listy zdrojů
        /// </summary>
        private List<LockInfoList> _ResourceListList;
        /// <summary>
        /// Soupis objektů šipek
        /// </summary>
        private List<LockInfoRelation> _RelationList;
        #endregion
        #region Příprava a rozmístění controlů
		private void _ShowData()
		{
			this._ClearDara();
			if (this._LockData != null && this._Config != null)
			{
				this._CreateGraphObjects();
				this._LayoutGraphObjects();
			}
		}
		/// <summary>
		/// Smaže všechny prvky zobrazující dat, ale nemaže _LockData a _Config
		/// </summary>
		private void _ClearDara()
		{
			this._VictimList = null;
			this._ProcessListList = new List<LockInfoList>();
			this._SpidListList = new List<LockInfoList>();
			this._RelationList = new List<LockInfoRelation>();
			this._Sheet.Controls.Clear();
			this.SummaryProcess = "";
			this.SummarySpid = "";
			this.SummaryResource = "";
		}
		/// <summary>
		/// Vytvoří objekty pro zobrazení (do this polí a do this.Control)
		/// </summary>
        private void _CreateGraphObjects()
        {
            SqlLockData lockData = this.LockData;
            if (lockData == null) return;

            Point location;
            Point maxPoint = Point.Empty;
			int sumVis, sumInv;

            this._ProcessListList = new List<LockInfoList>();
			this.SummaryProcess = "";

            // Procesy
			sumVis = 0;
			sumInv = 0;
            this._SpidListList = new List<LockInfoList>();
            location = new Point(this.Padding.Left, this.Padding.Top + 14);
			foreach (SqlSpidInfo spidInfo in lockData.SpIdList)
            {
				bool isVictim = spidInfo.IsVictim;
				if (isVictim || this.Config.IsSpidVisible(spidInfo))
				{
					sumVis++;
					LockInfoList list = LockInfoCreator.CreateListFromProcess(lockData.EventInfo, spidInfo);
					if (list != null)
					{
						list.Location = location;
						list.Size = list.MinimumSize;
						this._Sheet.Controls.Add(list);
						this._SpidListList.Add(list);
						if (isVictim)
							this._VictimList = list;
						maxPoint = new Point((maxPoint.X > list.Right ? maxPoint.X : list.Right), (maxPoint.Y > list.Bottom ? maxPoint.Y : list.Bottom));

						location = new Point(location.X + list.Width + 6, location.Y);
					}
				}
				else
				{
					sumInv++;
				}
            }
			this.SummarySpid = sumVis.ToString() + (sumInv > 0 ? " (+" + sumInv.ToString() + ")" : "");

            // Prostor pro šipky:
            Rectangle arrowArea = new Rectangle(6, maxPoint.Y + 6, maxPoint.X - 6, 50);
            int height = arrowArea.Width / 6;
            if (arrowArea.Height < height)
                arrowArea.Height = height;

            // Zdroje
			sumVis = 0;
			sumInv = 0;
            this._ResourceListList = new List<LockInfoList>();
            location = new Point(6, arrowArea.Bottom + 6);
            foreach (SqlResourceInfo resource in lockData.ResourceList)
            {
				if (this.Config.IsResourceVisible(resource))
				{
					sumVis++;
					LockInfoList list = LockInfoCreator.CreateListFromResource(lockData.TraceData, resource);
					if (list != null)
					{
						list.Location = location;
						list.Size = list.MinimumSize;
						this._Sheet.Controls.Add(list);
						this._ResourceListList.Add(list);
						maxPoint = new Point((maxPoint.X > list.Right ? maxPoint.X : list.Right), (maxPoint.Y > list.Bottom ? maxPoint.Y : list.Bottom));

						location = new Point(location.X + list.Width + 6, location.Y);
					}
				}
				else
				{
					sumInv++;
				}
			}
			this.SummaryResource = sumVis.ToString() + (sumInv > 0 ? " (+" + sumInv.ToString() + ")" : "");

            // Vytvořit šipky:
            this._RelationList = new List<LockInfoRelation>();
            foreach (SqlResourceInfo resource in lockData.Resources.Values)
                this._RelationList.AddRange(LockInfoCreator.CreateRelations(resource));
            foreach (LockInfoRelation relation in this._RelationList)
            {
                relation.SpidView = this._SpidListList.FirstOrDefault(v => v.ContentId == relation.SpidInfo.SpID);           // najdu View, které zobrazuje SPID
                relation.ResourceView = this._ResourceListList.FirstOrDefault(v => v.ContentId == relation.ResourceInfo.Id); // najdu View, které zobrazuje Resource
                if (relation.IsLinkedToSpid)
                    relation.VisualArrow = new ArrowRelation();
            }
            this._RelationList.Sort(LockInfoRelation.CompareByLayer);
        }
        private void _LayoutGraphObjects()
        {
            // Určím velikost obsazené plochy:
            Point lastPoint = Point.Empty;
            _LayoutObjectGetMaxPoint(this._ProcessListList, ref lastPoint);
			_LayoutObjectGetMaxPoint(this._SpidListList, ref lastPoint);
            _LayoutObjectGetMaxPoint(this._ResourceListList, ref lastPoint);
			lastPoint = new Point(lastPoint.X + this.Padding.Right, lastPoint.Y + this.Padding.Bottom);
            Size allSize = new Size(lastPoint);
			this._Sheet.Size = allSize;

            // Uložíme zjištěnou optimální velikost, aby se mohl Owner Form pokusit o přizpůsobení své velikosti:
            Size contentSize = new Size(allSize.Width + this.Padding.Horizontal, allSize.Height + this.Padding.Vertical);
            this.ContentSize = contentSize;

			// Můžeme najít tu řadu, která je "nejkratší", abychom její controly lépe rozšířili:
			int spidMaxX = this._SpidListList.Max(q => q.Right);
			int resourceMaxX = this._ResourceListList.Max(q => q.Right);
			int maxX = Math.Max(spidMaxX, resourceMaxX);

            // Pozice a vlastnosti šipek:
            foreach (LockInfoRelation lockRel in this._RelationList.Where(r => r.HasArrow))
            {
                ArrowRelation arrow = lockRel.VisualArrow;
                PointF processPoint = lockRel.SpidView.GetPointOnActiveEdge(lockRel.ResourceView, allSize, 8, 0.15f);
                PointF resourcePoint = lockRel.ResourceView.GetPointOnActiveEdge(lockRel.SpidView, allSize, 8, 0.15f);
                if (lockRel.IsOwner)
                {   // Proces je vlastníkem: šipka vede ze zdroje na proces, je širší a zelená:
                    arrow.Begin = resourcePoint;
                    arrow.End = processPoint;
                    arrow.Width = 17;
                    arrow.WidthArrow = 8;
                    arrow.ArrowLength = 35;
                    arrow.ArrowOverhung = 12;
                    arrow.ArrowSlant = -2;
                    arrow.UseSlantBrush = true;
                    arrow.Color1 = Color.FromArgb(191, 239, 186);
                    arrow.Color2 = Color.FromArgb(109, 175, 127);
                    arrow.ColorLine = Color.Black;
                    arrow.Text = lockRel.RelationSpidResource.ModeText;
                }
                else
                {   // Proces je žadatelem: šipka vede ze procesu na zdroj, je tenká a žlutá:
                    
                    arrow.Begin = processPoint;
                    arrow.End = resourcePoint;
                    arrow.Width = 8;
                    arrow.WidthArrow = -3;
                    arrow.ArrowLength = 16;
                    arrow.ArrowOverhung = 7;
                    arrow.ArrowSlant = 4;
                    arrow.UseSlantBrush = false;
                    if (lockRel.SpidInfo.IsVictim)
                    {   // Pokud je aktuální proces obětí deadlocku, pak je šipka červená:
                        arrow.Color1 = Color.LightCoral;
                        arrow.Color2 = Color.Firebrick;
                    }
                    else
                    {
                        arrow.Color1 = Color.FromArgb(255, 250, 205);
                        arrow.Color2 = Color.FromArgb(194, 170, 85);
                    }
                    arrow.ColorLine = Color.Black;
                    arrow.Text = lockRel.RelationSpidResource.ModeText;
                }
            }

			this._Sheet.Refresh();
        }

        private void _LayoutObjectGetMaxPoint(List<LockInfoList> listList, ref Point lastPoint)
        {
            foreach (LockInfoList list in listList)
            {
                if (list.Right > lastPoint.X) lastPoint.X = list.Right;
                if (list.Bottom > lastPoint.Y) lastPoint.Y = list.Bottom;
            }
        }
        #endregion
        #region Vykreslení přidané grafiky
        private void _PaintRelations(Graphics graphics)
        {
            if (this._RelationList == null) return;

            var state = Arrow.PrepareGraphics(graphics);

            // Vykreslit oběť deadlocku:
            Control victim = this._VictimList;
            if (victim != null)
            {
                Rectangle victimBound = victim.Bounds;
                using (Font font = new System.Drawing.Font(this.Font, FontStyle.Bold))
                {
                    string text = "Deadlock victim, time=" +
                        (this.LockData.DeadlockTime.HasValue ? this.LockData.DeadlockTime.Value.ToString("HH:mm:ss.fff") : "???");
                    Size size = Size.Round(graphics.MeasureString(text, font));

                    victimBound = new Rectangle(victimBound.X - 3, victimBound.Y - size.Height - 5, victimBound.Width + 6, victimBound.Height + size.Height + 8);
                    RoundEdgeRectangle victimArea = new RoundEdgeRectangle(victimBound, 7, 4);
                    using (LinearGradientBrush lgb = new LinearGradientBrush(victimBound, Color.LightCoral, Color.Firebrick, 90f))
                    {
                        graphics.FillPath(lgb, victimArea.Path);
                    }

                    Point begin = new Point(victimBound.X + (victimBound.Width - size.Width) / 2, victimBound.Y + 4);
                    graphics.DrawString(text, font, Brushes.Black, begin);
                }
            }

            // Vykreslení šipek:
            foreach (LockInfoRelation lockRel in this._RelationList.Where(r => r.HasArrow))
                lockRel.VisualArrow.DrawArrow(graphics);

            // Vykreslení textů u šipek (až po vykreslení všech šipek, aby se kreslilo přes vrch):
            foreach (LockInfoRelation lockRel in this._RelationList.Where(r => r.HasArrow))
                lockRel.VisualArrow.DrawText(graphics, this.Font);

            graphics.Restore(state);
        }
        #endregion
    }
}
